import java.awt.Point;
import java.util.LinkedList;

import CLIPSJNI.PrimitiveValue;

public class ClipsDecisionProvider {

	private Agent agent;
	
	public Agent getAgent() {
		return agent;
	}

	public void setAgent(Agent agent) {
		this.agent = agent;
	}
	
	private Action action;

	public ClipsDecisionProvider(Agent agent) {
		this.agent = agent;
	}

	public Action decide() {
		Main.getClips().reset();
		assertKnowledge();
		Thread t = new Thread(new ClipsPrompter());
		t.start();
		while (t.isAlive()) {
			// wait
		}
		if (Main.isPrintAction()) {
			getAction().print();
		}
		return getAction();
	}


	private void assertKnowledge() {

		for(Tree tree : Main.getMap().getTrees()) {
			Point lp = tree.getPosition();
			Main.getClips().assertString(
			"(map-object (type \"tree\") (id " + tree.getId() + ") (x " + lp.x + ") (y "
			+ lp.y + ") (energy " + tree.getAppleCount() + " ))");
		}

		@SuppressWarnings("unchecked")
		LinkedList<Human> humans = (LinkedList<Human>) Main.getMap().getHumans().clone();
		for (Human human : humans) {
			//if (!b.isOccupied()) {
				Main.getClips().assertString(
						"(map-object (type \"human\") (id " + human.getId()
								+ ") (x " + human.getPosition().x + ") (y "
								+ human.getPosition().y + ") (energy " + human.getEnergy()+"))");

				for (Direction d : human.isNextToEmptySpace()) {
					MapObject wo = MapUtils.getAdjecentObject(human.getPosition(), d);
					Main.getClips().assertString(
							"(near-empty (target-id "
									+ human.getId() + ") (direction \""
									+ directionToString(d)
									+ "\") (id 0) (x "
									+ wo.getPosition().x + ") (y "
									+ wo.getPosition().y
									+ "))");
				}
			}
		@SuppressWarnings("unchecked")
		LinkedList<Zombie> zombies = (LinkedList<Zombie>) Main.getMap().getZombies().clone();
		for (Zombie b : zombies) {
			//if (!b.isOccupied()) {
				Main.getClips().assertString(
						"(map-object (type \"zombie\") (id " + b.getId()
								+ ") (x " + b.getPosition().x + ") (y "
								+ b.getPosition().y + ") (energy " + b.getEnergy()+"))");

				for (Direction d : b.isNextToEmptySpace()) {
					MapObject wo = MapUtils.getAdjecentObject(b.getPosition(), d);
					Main.getClips().assertString(
							"(near-empty (target-id "
									+ b.getId() + ") (direction \""
									+ directionToString(d)
									+ "\") (id 0) (x "
									+ wo.getPosition().x + ") (y "
									+ wo.getPosition().y
									+ "))");
				}

			}
		
		@SuppressWarnings("unchecked")
		LinkedList<Machine> machines = (LinkedList<Machine>) Main.getMap().getMachines().clone();
		for (Machine machine : machines) {
			//if (!b.isOccupied()) {
				Main.getClips().assertString(
						"(map-object (type \"machine\") (id " + machine.getId()
								+ ") (x " + machine.getPosition().x + ") (y "
								+ machine.getPosition().y + ") (energy " + machine.getEnergy()+"))");

				for (Direction d : machine.isNextToEmptySpace()) {
					MapObject wo = MapUtils.getAdjecentObject(machine.getPosition(), d);
					Main.getClips().assertString(
							"(near-empty (target-id "
									+ machine.getId() + ") (direction \""
									+ directionToString(d)
									+ "\") (id 0) (x "
									+ wo.getPosition().x + ") (y "
									+ wo.getPosition().y
									+ "))");
				}

			}
		}
	
	public Action getAction() {
		return action;
	}

	public void setAction(Action action) {
		this.action = action;
	}

	private String directionToString(Direction d) {
		return d.toString().toLowerCase();
	}

	private class ClipsPrompter implements Runnable {

		@Override
		public void run() {
			Main.getClips().run();
			try {
				promptClips(getAgent().getId());
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		private void promptClips(long id) {
			String evalStr = "(MAP::get-action)";
			PrimitiveValue pv = Main.getClips().eval(evalStr);
			LinkedList<PrimitiveValue> actions = new LinkedList<PrimitiveValue>();
			try {

				if (pv.size() > 0) {
					
					for(int i = 0; i < pv.size(); i++)
					{
						PrimitiveValue fv = pv.get(i);
						if(fv.getFactSlot("id").intValue()==id)
						{
							actions.add(fv);
						}
					}
					
					if(actions.size() > 0)
					{

						int index =  (int) Math.round((Math.random() * (actions.size() - 1)));

						setAction(resolveAction(actions.get(index)));
					}
					else
						setAction(Action.NO_ACTION);				
					
				} else {
					setAction(Action.NO_ACTION);
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}
		}

		private Action resolveAction(PrimitiveValue fv) {
			try {
				String name = fv.getFactSlot("name").stringValue();
				String type = fv.getFactSlot("type").stringValue();
				int id = fv.getFactSlot("id").intValue();
				int targetId = fv.getFactSlot("target-id").intValue();
				Action a = new Action(name, resolveType(type), id);
				a.setTargetId(targetId);
				try {
					String direction = fv.getFactSlot("direction").stringValue();
					
					a.setDirection(resolveDirection(direction));
				} catch (Exception e) {
					// donothing
				}
				return a;
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(0);
			}
			return Action.NO_ACTION;
		}

		private MapObjectType resolveType(String t) {
			switch (t) {
			case "human":
				return MapObjectType.Human;
			case "zombie":
				return MapObjectType.Zombie;
			case "tree":
				return MapObjectType.Tree;
			case "machine":
				return MapObjectType.Machine;
			case "plant":
				return MapObjectType.PowerPlant;
			case "empty-space":
				return MapObjectType.EMPTY_SPACE;
			case "no-object":
				return MapObjectType.NO_OBJECT;
			default:
				return null;
			}
		}

		private Direction resolveDirection(String d) {
			switch (d) {
			case "left":
				return Direction.LEFT;
			case "right":
				return Direction.RIGHT;
			case "up":
				return Direction.UP;
			case "down":
				return Direction.DOWN;
			default:
				return null;
			}
		}

	}
}
